﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Pacol
{
    public class Labyrinth
    {
        public Block [,] _block;
        private int _width;
        private int _height;
        private int _blockSize;
        private int _thicknessBorder;
        private uint _seed;
        private Color[] _color;
        private Color _playerColor;
        private Texture2D _wallTex;
        private List<List<Block>> _respawnArea;
        
        // procedural constructor
        public Labyrinth(int width, int height, int blockSize, int thicknessBorder, uint seed, Texture2D wallTex, Color color0, Color color1, Color playerColor)
        {
            _block = new Block[width, height];
            _width = width;
            _height = height;
            _blockSize = blockSize;
            _seed = seed;
            _wallTex = wallTex;
            _respawnArea = new List<List<Block>>();

            //Calculate the tickness of the border (15% of the blocSize)
            _thicknessBorder = thicknessBorder;

            _color = new Color[2];
            _color[0] = color0;
            _color[1] = color1;

            _playerColor = playerColor;

            ColorWall();

            DelimitRespawnArea(0, 0, new bool[width, height], 0, 0, color0);
            DelimitRespawnArea(0, 0, new bool[width, height], 0, 0, color1);
        }

        private void ColorWall()
        {
            Color colorWallT;
            Color colorWallR;
            Color colorWallB;
            Color colorWallL;
            int wallCounter = 0;
            uint seedTmp = _seed;

            // for each blocks
            for (int i = 0; i < _width; i++)
            {
                for (int j = 0; j < _height; j++)
                {
                    string seedTmpStr = seedTmp.ToString();

                    // top wall color
                    seedTmp += 1 + (uint)Char.GetNumericValue(seedTmpStr[wallCounter++ % seedTmpStr.Count()]); // +1 to be sure to change the color in case _seedStr[x] = 0
                    colorWallT = _color[seedTmp % _color.Count()];

                    // right wall color
                    seedTmp += 1 + (uint)Char.GetNumericValue(seedTmpStr[wallCounter++ % seedTmpStr.Count()]);
                    colorWallR = _color[seedTmp % _color.Count()];

                    // bottom wall color
                    seedTmp += 1 + (uint)Char.GetNumericValue(seedTmpStr[wallCounter++ % seedTmpStr.Count()]);
                    colorWallB = _color[seedTmp % _color.Count()];

                    // left wall color
                    seedTmp += 1 + (uint)Char.GetNumericValue(seedTmpStr[wallCounter++ % seedTmpStr.Count()]);
                    colorWallL = _color[seedTmp % _color.Count()];

                    _block[i, j] = new Block(new Color(colorWallT.ToVector3().X, colorWallT.ToVector3().Y, colorWallT.ToVector3().Z), new Color(colorWallR.ToVector3().X, colorWallR.ToVector3().Y, colorWallR.ToVector3().Z), new Color(colorWallB.ToVector3().X, colorWallB.ToVector3().Y, colorWallB.ToVector3().Z), new Color(colorWallL.ToVector3().X, colorWallL.ToVector3().Y, colorWallL.ToVector3().Z), i * _blockSize, j * _blockSize);
                }
            }

            TwinWall();

            if(_color[0].Equals(_playerColor))
                PaintBorder(_color[1]);
            else
                PaintBorder(_color[0]);
        }

        // semi-detached walls must have the same color
        private void TwinWall()
        {
            // for each blocks
            for (int i = 0; i < _width - 1; i++)
            {
                for (int j = 0; j < _height - 1; j++)
                {
                    _block[i, j].colorBottom = _block[i, j + 1].colorTop;
                    _block[i, j].colorRight = _block[i + 1, j].colorLeft;
                }
            }

            // for each blocks on the last column
            for (int i = 0; i < _height - 1; i++)
                _block[_width - 1, i].colorBottom = _block[_width - 1, i + 1].colorTop;

            // for each blocks on the last line
            for (int i = 0; i < _width - 1; i++)
                _block[i, _height - 1].colorRight = _block[i + 1, _height -1].colorLeft;
        }


        // delimit closed area where the enemies can respawn
        // on first call x = 0, y = 0, areaNumber = 0, blockNumber = 0, visitedBlock = bool[false, false]
        private void DelimitRespawnArea(int x, int y, bool[,] visitedBlock, int areaNumber, int blockNumber, Color areaColor)
        {
            if (x >= 0 && y >= 0 && x < _width && y < _height && !visitedBlock[x, y])
            {
                visitedBlock[x, y] = true;

                if (blockNumber == 0)
                    _respawnArea.Add(new List<Block>());

                _respawnArea[_respawnArea.Count - 1].Add(_block[x, y]);

                // the area can be extend upwardly
                if (!_block[x, y].colorTop.Equals(areaColor) && y > 0)
                    DelimitRespawnArea(x, y - 1, visitedBlock, areaNumber, _respawnArea[areaNumber].Count + 1, areaColor);

                // the area can be extend to the right
                if (!_block[x, y].colorRight.Equals(areaColor) && x + 1 < _width)
                    DelimitRespawnArea(x + 1, y, visitedBlock, areaNumber, _respawnArea[areaNumber].Count + 1, areaColor);

                // the area can be extend downwardly
                if (!_block[x, y].colorBottom.Equals(areaColor) && y + 1 < _height)
                    DelimitRespawnArea(x, y + 1, visitedBlock, areaNumber, _respawnArea[areaNumber].Count + 1, areaColor);

                // the area can be extend to the left
                if (!_block[x, y].colorLeft.Equals(areaColor) && x > 0)
                    DelimitRespawnArea(x - 1, y, visitedBlock, areaNumber, _respawnArea[areaNumber].Count + 1, areaColor);
            }

            if (blockNumber == 0)
            {
                x++;
                if (x == _width)
                {
                    x = 0;
                    y++;
                }

                // we delete the one-block areas
                for(int i = 0; i < _respawnArea.Count; i++)
                    if (_respawnArea[i].Count == 1)
                        _respawnArea.RemoveAt(i);

                if(y < _height)
                    DelimitRespawnArea(x, y, visitedBlock, _respawnArea.Count, 0, areaColor);
            }
        }

        public void Draw(SpriteBatch spriteBatch, Color playerColor)
        {
            for (int i = 0; i < _width; i++)
            {
                for (int j = 0; j < _height; j++)
                {
                    _block[i, j].DrawGround(spriteBatch, _blockSize, _thicknessBorder, playerColor);
                }
            }

            for (int i = 0; i < _width; i++)
            {
                for (int j = 0; j < _height; j++)
                {
                    _block[i, j].DrawBorder(spriteBatch, _blockSize, _thicknessBorder, playerColor);
                }
            }

            //DrawBorder(spriteBatch, Color.Brown);
        }

        /*private void DrawBorder(SpriteBatch spriteBatch, Color borderColor)
        {
            for (int i = 0; i < _width; i++)
            {
                // top border
                spriteBatch.Draw(_wallTex, new Rectangle(_block[i, 0].x, 0, _blockSize, Game1._thicknessBorder), borderColor);
                // bottom border
                spriteBatch.Draw(_wallTex, new Rectangle(_block[i, _height - 1].x, _height * _blockSize - Game1._thicknessBorder, _blockSize, Game1._thicknessBorder), borderColor);
            }

            for (int i = 0; i < _height; i++)
            {
                // left border
                spriteBatch.Draw(_wallTex, new Rectangle(0, _block[0, i].y, Game1._thicknessBorder, _blockSize), borderColor);
                // right border
                spriteBatch.Draw(_wallTex, new Rectangle(_width * _blockSize - Game1._thicknessBorder, _block[_width - 1, i].y, Game1._thicknessBorder, _blockSize), borderColor);
            }
        }*/

        public void PaintBorder(Color borderColor)
        {
            for (int i = 0; i < _width; i++)
            {
                // top border
                _block[i, 0].colorTop = borderColor;
                // bottom border
                _block[i, _height - 1].colorBottom = borderColor;
            }

            for (int i = 0; i < _height; i++)
            {
                // left border
                _block[0, i].colorLeft = borderColor;
                // right border
                _block[_width - 1, i].colorRight = borderColor;
            }
        }

        public Block[,] block
        {
            get { return _block; }
        }

        public int width
        {
            get { return _width; }
        }

        public int height
        {
            get { return _height; }
        }

        public Block GetBlockAt(int x, int y)
        {
            return _block[x / _blockSize, y / _blockSize];
        }

        public List<List<Block>> respawnArea
        {
            get { return _respawnArea; }
        }

        public int blockSize
        {
            get { return _blockSize; }
        }
    }
}